package net.sh.dpinfrastructure.http;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.CodingErrorAction;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.MessageConstraints;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import net.sh.dpinfrastructure.config.DPConfig;
import net.sh.dpinfrastructure.exception.DPException;
import net.sh.dpinfrastructure.log.DPLogger;

/**
 * 
 * {@link net.sh.dpinfrastructure.http.DPHttp}基于{@link HttpClient}的实现类
 * 
 * @author Lancer
 * @date 2015年9月13日下午6:05:37
 * 
 */
public class DPHttpClientHttp implements DPHttp {

    private static final DPLogger LOGGER = new DPLogger(DPHttp.class);
    
    private CloseableHttpClient httpClient;
    
    public DPHttpClientHttp(final DPConfig config) {
        try {
            // 连接池
            // https安全连接
            SSLContext sslContext = SSLContexts.custom().useProtocol("TLS").build();
            sslContext.init(null,
                    new TrustManager[] { new X509TrustManager() {
                         
                        public void checkClientTrusted(
                                X509Certificate[] certs, String authType) {
                        }

                        public void checkServerTrusted(
                                X509Certificate[] certs, String authType) {
                        }

                        public X509Certificate[] getAcceptedIssuers() {
                            return null;
                        }
                    }}, null);
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.INSTANCE)
                    .register("https", new SSLConnectionSocketFactory(sslContext))
                    .build();
            
            PoolingHttpClientConnectionManager phccm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            phccm.setMaxTotal(config.getIntValue("http.total", 200));// 设置最大连接数200
            phccm.setDefaultMaxPerRoute(config.getIntValue("http.total", 3));// 设置每个路由默认连接数
            SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();
            phccm.setDefaultSocketConfig(socketConfig);
            // Create message constraints
            MessageConstraints messageConstraints = MessageConstraints.custom()
                .setMaxHeaderCount(200)
                .setMaxLineLength(2000)
                .build();
            
            // Create connection configuration
            ConnectionConfig connectionConfig = ConnectionConfig.custom()
                .setMalformedInputAction(CodingErrorAction.IGNORE)
                .setUnmappableInputAction(CodingErrorAction.IGNORE)
                .setCharset(Consts.UTF_8)
                .setMessageConstraints(messageConstraints)
                .build();
            
            phccm.setDefaultConnectionConfig(connectionConfig);
            
            // 设置请求和传输超时时间
            RequestConfig requestConfig = RequestConfig
                .custom()
                .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
                .setSocketTimeout(config.getIntValue("http.socketTimeout", 15000))
                .setConnectTimeout(config.getIntValue("http.setConnectTimeout", 10000))
                .build();
            final int retryCount = config.getIntValue("http.retry.count", 3);
            httpClient = HttpClients
                .custom()
                .setConnectionManager(phccm)
                .setDefaultRequestConfig(requestConfig)
                .setRetryHandler(new DefaultHttpRequestRetryHandler() {
                    
                    /** 
                     * 
                     * @author shaohui
                     * @date 2017年2月2日下午12:35:34
                     * @see org.apache.http.impl.client.DefaultHttpRequestRetryHandler#getRetryCount()
                     */ 
                    @Override
                    public int getRetryCount() {
                        return retryCount;
                    }
                    
                    /** 
                     * {@inheritDoc}<br>
                     * 
                     * @author shaohui
                     * @date 2017年2月2日下午1:44:15
                     * @see org.apache.http.impl.client.DefaultHttpRequestRetryHandler#retryRequest(java.io.IOException, int, org.apache.http.protocol.HttpContext)
                     */ 
                    @Override
                    public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                        return super.retryRequest(exception, executionCount, context);
                    }
                })
                .build();
            LOGGER.info("http initialized");
//            httpClient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler());
//            httpClient.setReuseStrategy(new DefaultConnectionReuseStrategy());
//            httpClient.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());
        } catch (Exception e) {
            throw new DPException("http服务初始化失败！", e);
        }
    }

    /**
     * 
     * 为http请求加上entity body，忽略key为空的项
     *
     * @param request
     * @param paramsMap
     * 
     * @author shaohui
     * @date 2016年8月11日 上午10:49:39
     */
    private void appendEntityBody(HttpEntityEnclosingRequestBase request, Map<String, String> paramsMap) {
        if (MapUtils.isEmpty(paramsMap)) {
            return;
        }
        
        List<NameValuePair> formParams = new ArrayList<NameValuePair>();
        for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
            String paramName = entry.getKey();
            if (StringUtils.isBlank(paramName)) {
                continue;
            }
            formParams.add(new BasicNameValuePair(paramName, entry.getValue()));
        }
        if (CollectionUtils.isEmpty(formParams)) {
            return;
        }
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
        request.setEntity(entity);
    }

    /**
     * 
     * 为http请求加上entity body及请求头
     *
     * @param request
     * @param headersMap
     * @param paramsMap
     * 
     * @author shaohui
     * @date 2016年8月11日 上午10:58:54
     */
    private void appendEntityBodyAndHeaders(HttpEntityEnclosingRequestBase request, Map<String, String> headersMap, Map<String, String> paramsMap) {
        appendHeaders(request, headersMap);
        appendEntityBody(request, paramsMap);
    }
    
    /**
     * 
     * 为http请求加上请求头，忽略key，value任一为空的项
     *
     * @param request
     * @param headers
     * 
     * @author shaohui
     * @date 2016年8月11日 上午10:40:07
     */
    private void appendHeaders(HttpRequestBase request, Map<String, String> headerMap) {
        if (MapUtils.isEmpty(headerMap)) {
            return;
        }
        for (Map.Entry<String, String> entry : headerMap.entrySet()) {
            String headName = entry.getKey();
            String headValue = entry.getValue();
            if (StringUtils.isBlank(headName) || StringUtils.isBlank(headValue)) {
                continue;
            }
////            try {
//                request.addHeader(headName, /*URLEncoder.encode(headValue, "utf-8")*/ headValue);
////            } catch (UnsupportedEncodingException e) {
////                throw new DPException("不支持的字符编码", e);
////            }
            request.setHeader(headName, headValue);
        }
    }
    
    /**
     * 
     * 关闭
     *
     * @throws IOException
     * 
     * @author shaohui
     * @date 2016年8月8日 下午2:12:21
     */
    public void closeClient() {
        if (httpClient != null) {
            
        }
        try {
            httpClient.close();
        } catch (IOException e) {
            throw new DPException("关闭httpclient失败", e);
        }
        LOGGER.info("httpclient closed");
    }
    
    @Override
    public DPHttpResponse delete(String url) {
        return delete(url, null, null);
    }
    
    @Override
    public DPHttpResponse delete(String url, Map<String, String> params) {
        return delete(url, null, params);
    }
    
    @Override
    public DPHttpResponse delete(String url, Map<String, String> headParams, Map<String, String> params) {
    	return request(DPHttpRequestMethod.DELETE, url, headParams, params);
    }
    
    @Override
    public DPHttpResponse get(String url) {
        return get(url, null, null);
    }

    @Override
    public DPHttpResponse get(String url, Map<String, String> params) {
        return get(url, null, params);
    }

    @Override
    public DPHttpResponse get(String url, Map<String, String> headParams, Map<String, String> params) {
        return request(DPHttpRequestMethod.GET, url, headParams, params);
    }
    
    public CloseableHttpClient getHttpClient() {
        return httpClient;
    }

    @Override
    public DPHttpResponse post(String url) {
        return this.post(url, null, null);
    }

    @Override
    public DPHttpResponse post(String url, Map<String, String> params) {
        return this.post(url, null, params);
    }

    @Override
    public DPHttpResponse post(String url, Map<String, String> headParams, Map<String, String> params) {
        return request(DPHttpRequestMethod.POST, url, headParams, params);
    }
    
    @Override
    public DPHttpResponse put(String url) {
        return put(url, null, null);
    }
    
    @Override
    public DPHttpResponse put(String url, Map<String, String> params) {
    	return put(url, null, params);
    }
    
    @Override
    public DPHttpResponse put(String url, Map<String, String> headParams, Map<String, String> params) {
    	return request(DPHttpRequestMethod.PUT, url, headParams, params);
    }

    @Override
    public DPHttpResponse request(DPHttpRequestMethod requestMethod, String url, Map<String, String> headParams,
                                  Map<String, String> params) {
        HttpRequestBase request = null;
        switch (requestMethod) {
            case GET: {
                request = new HttpGet();
                break;
            }
            case POST: {
                request = new HttpPost();
                break;
            }
            case PUT: {
                request = new HttpPut();
                break;
            }
            case DELETE: {
                request = new HttpDelete();
                break;
            }
            default: {
                throw new DPException("不支持的http请求类型");
            }
        }
        if (request instanceof HttpEntityEnclosingRequestBase) {
            try {
                request.setURI(new URI(url));
            } catch (URISyntaxException e) {
                throw new DPException("url不正确", e);
            }
            appendEntityBodyAndHeaders((HttpEntityEnclosingRequestBase) request, headParams, params);
        } else {
            try {
                request.setURI(new URI(DPHttp.createUrlWithParams(url, params)));
            } catch (URISyntaxException e) {
                throw new DPException("url不正确", e);
            }
            appendHeaders(request, headParams);
        }
        
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(request);
        } catch (ClientProtocolException e1) {
            throw new DPException("http连接失败", e1);
        } catch (IOException e1) {
            throw new DPException("http连接失败", e1);
        };
        
        HttpEntity entity = response.getEntity();
        byte[] content = null;
        try {
            content = EntityUtils.toByteArray(entity);
        } catch (IOException e1) {
            throw new DPException("http 连接被中断", e1);
        }
        try {
            return new DPHttpResponse(response.getStatusLine().getStatusCode(), entity.getContentLength(), content);
        } catch (ParseException e) {
            throw new DPException("http response解析失败", e);
        }
    }
}
